package nayasis.common.db.manager.sql.condition.node;

import java.util.List;
import java.util.Map;

import nayasis.common.db.manager.sql.ParamValue;
import nayasis.common.db.manager.sql.type.Command;
import nayasis.common.db.manager.sql.type.Type;
import nayasis.common.exception.unchecked.SyntaxException;
import nayasis.common.util.StringUtil;

public class CommandNode extends Node {

    private Command command;

    public CommandNode( Command command ) {
        super( Type.command );
        this.command = command;
    }
    
    public Command get() {
        return this.command;
    }

    public boolean test( Node prevNode, Node nextNode, Map<String, ParamValue> bindingParam ) {

        ParamValue prevVal = getValue( prevNode, bindingParam );
        ParamValue nextVal = getValue( nextNode, bindingParam );

        if( prevVal.isNull() ) {
            throw new SyntaxException( "There is no value to test. [@ @ @]", prevVal, command, nextVal );
        }
        
        if( nextVal.isNull() ) {
            
            if( this.command != Command.isEmpty && this.command != Command.isNotEmpty ) {
                throw new SyntaxException( "There is no value to test. [@ @ @]", prevVal, command, nextVal );
            }
            
        }
        
        switch( this.command ) {

            case isEmpty :
                return isEmpty( prevVal );
                
            case isNotEmpty :
                return ! isEmpty( prevVal );
            
            case equal :
                return equal( prevVal, nextVal );
                
            case notEqual :
                return ! equal( prevVal, nextVal );
                
            case greaterThan :
                return greaterThan( prevVal, nextVal );
                
            case greaterEqual :
                return greaterEqual( prevVal, nextVal );
                
            case lessThan :
                return lessThan( prevVal, nextVal );

            case lessEqual :
                return lessEqual( prevVal, nextVal );

            
        }
        
        
        return true;
    }

    @SuppressWarnings( "rawtypes" )
    private boolean isEmpty( ParamValue prevVal ) {

        if( prevVal.isArray() ) {
            return ( (List) prevVal ).size() == 0;
        } else {
            return StringUtil.isEmpty( prevVal );
        }
        
    }
    
    
    @SuppressWarnings( "rawtypes" )
    private boolean equal( ParamValue prevVal, ParamValue nextVal ) {

        Object prevObj = prevVal.getValue();
        Object nextObj = nextVal.getValue();

        if(   prevVal.isArray() && ! nextVal.isArray() ) return false;
        if( ! prevVal.isArray() &&   nextVal.isArray() ) return false;
        
        if( prevVal.isArray() && nextVal.isArray() ) {
            
            List prevList = (List) prevObj;
            List nextList = (List) nextObj;
            
            if( prevList.size() != nextList.size() ) return false;
            
            for( int i = 0, iCnt = prevList.size(); i < iCnt; i++ ) {
                if( ! prevList.get(i).equals( nextList.get(i) ) ) return false;
            }
            
            return true;
            
        } else if( prevVal.isNumeric() && nextVal.isNumeric() ){
            return ( (Double) prevObj ) == ( (Double) nextObj );
        } else {
            return prevObj.toString().equals( nextObj.toString() );
        }
        
    }

    @SuppressWarnings( "rawtypes" )
    private boolean greaterThan( ParamValue prevVal, ParamValue nextVal ) {
        
        Object prevObj = prevVal.getValue();
        Object nextObj = nextVal.getValue();
        
        if( prevVal.isArray() && nextVal.isArray() ) {
            
            List prevList = (List) prevObj;
            List nextList = (List) nextObj;
            
            if( prevList.size() != nextList.size() ) return false;
            
            return prevList.toString().compareTo( nextList.toString() ) > 0;
            
        } else if( prevVal.isNumeric() && nextVal.isNumeric() ){
            return ( (Double) prevObj ) > ( (Double) nextObj );
        } else {
            return prevObj.toString().compareTo( nextObj.toString() ) > 0;
        }
        
    }

    @SuppressWarnings( "rawtypes" )
    private boolean greaterEqual( ParamValue prevVal, ParamValue nextVal ) {
        
        Object prevObj = prevVal.getValue();
        Object nextObj = nextVal.getValue();
        
        if( prevVal.isArray() && nextVal.isArray() ) {
            
            List prevList = (List) prevObj;
            List nextList = (List) nextObj;
            
            if( prevList.size() != nextList.size() ) return false;
            
            return prevList.toString().compareTo( nextList.toString() ) >= 0;
            
        } else if( prevVal.isNumeric() && nextVal.isNumeric() ){
            return ( (Double) prevObj ) >= ( (Double) nextObj );
        } else {
            return prevObj.toString().compareTo( nextObj.toString() ) >= 0;
        }
        
    }

    @SuppressWarnings( "rawtypes" )
    private boolean lessThan( ParamValue prevVal, ParamValue nextVal ) {
        
        Object prevObj = prevVal.getValue();
        Object nextObj = nextVal.getValue();
        
        if( prevVal.isArray() && nextVal.isArray() ) {
            
            List prevList = (List) prevObj;
            List nextList = (List) nextObj;
            
            if( prevList.size() != nextList.size() ) return false;
            
            return prevList.toString().compareTo( nextList.toString() ) < 0;
            
        } else if( prevVal.isNumeric() && nextVal.isNumeric() ){
            return ( (Double) prevObj ) < ( (Double) nextObj );
        } else {
            return prevObj.toString().compareTo( nextObj.toString() ) < 0;
        }
        
    }
    
    @SuppressWarnings( "rawtypes" )
    private boolean lessEqual( ParamValue prevVal, ParamValue nextVal ) {
        
        Object prevObj = prevVal.getValue();
        Object nextObj = nextVal.getValue();
        
        if( prevVal.isArray() && nextVal.isArray() ) {
            
            List prevList = (List) prevObj;
            List nextList = (List) nextObj;
            
            if( prevList.size() != nextList.size() ) return false;
            
            return prevList.toString().compareTo( nextList.toString() ) <= 0;
            
        } else if( prevVal.isNumeric() && nextVal.isNumeric() ){
            return ( (Double) prevObj ) <= ( (Double) nextObj );
        } else {
            return prevObj.toString().compareTo( nextObj.toString() ) <= 0;
        }
        
    }
    
    private ParamValue getValue( Node node, Map<String, ParamValue> bindingParam ) {

        if( node == null ) {
            return new ParamValue( null );
        }
        
        switch( node.getType() ) {
            
            case variable :
                
                String variableName = ( (VariableNode) node ).getName();
                
                for( String key : bindingParam.keySet() ) {
                    
                    if( ! variableName.equals(key) ) continue;
                        
                    return bindingParam.get( key );
                    
                }
                
            case constant :
                
                return ( (ConstantNode) node ).getValue();

            default :
                
                return null;
            
        }
        
    }
    
    public String toString() {
        return this.command.toString();
    }
    
}
